/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package steg.steki.pm3;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import steg.steki.PlikSteg;
import steg.steki.StegException;
import steg.sekret.SekretInfo;
import steg.sekret.UnSekret;
import steg.utils.MojaLinkedHashMap;

//===================== FUNKCJE PRYWATNE ===================================
/**
 *
 * @author Tryllion
 */
public class PlikMP3 extends PlikSteg {


// =============================== ZIENNE PRYWATNE =============================
    /*private*/ static final byte[] TAG_ZNACZNIK = { (byte)0x57,(byte)0x58,(byte)0x58,(byte)0x57};

    private boolean id3Is = false;
    private int id3Len = 0;
    private String id3ver = "---";
    private boolean flagUnsynchronisation;
    private boolean flagExtendedHeader;
    private boolean flagExperimentalIndicator;
    private boolean flagFooterPresent;

    private boolean id3TAGIs = false;
    private long id3TAGloc = 0;


    /**TE DWIE CHYBA DO KASACJI*/
    private boolean fhIs = false; //firstHeader jest
    private boolean fh2Is = false; // 2Header jest
    private MP3FrameHeader fh1 = new MP3FrameHeader();;
    private MP3FrameHeader fh2 = new MP3FrameHeader();;

    private int fhIlosc = -1;
    private int fhStegIlosc = -1;
    private boolean usfEOT = false; //unsteg flag EOT

    


// =============================== FUNKCJE KRYTYCZNE ===========================
    public void init(File file) throws StegException, IOException {



        plik = file;
        // TRZEBA 3 KRTONIE OTWIERAC STRUMIEN ! :/


        // ------- SPRAWDZ ID3v2

        InputStream in = new FileInputStream(file);
        DataInputStream dataIn = new DataInputStream(in);

        try {
            byte[] trzyPierwsze = new byte[3];
            dataIn.readFully(trzyPierwsze);

            // CZY TO ID3 tag ? Najprawdopodobniej !
            if ((trzyPierwsze[0] == 'I') && (trzyPierwsze[1] == 'D') && (trzyPierwsze[2] == '3')) {
                id3Is = true;
                readID3Tag(dataIn);
            }


        } finally {
            //zamknac chocby nie wiem co!
            dataIn.close();
        }


        // ------------------- teraz TAG id3v1

        in = new FileInputStream(file);
        dataIn = new DataInputStream(in);
        try {
            // ostatnie 128 bitów to ten tag, tylko pozycje sczytać i czy jest.
            long dlugFile = file.length();
            dataIn.skip(dlugFile - 128);

            byte[] trzyPierwsze = new byte[3];
            dataIn.readFully(trzyPierwsze);

            if ((trzyPierwsze[0] == 'T') && (trzyPierwsze[1] == 'A') && (trzyPierwsze[2] == 'G')) {
                id3TAGIs = true;
                id3TAGloc = (dlugFile - 128);
            } else {
                id3TAGIs = false;
                id3TAGloc = (dlugFile);
            }


        } finally {
            //zamknac chocby nie wiem co!
            dataIn.close();
        }

        // TERAZ WŁĄŚĆIWY frame header
        in = new FileInputStream(file);
        dataIn = new DataInputStream(in);
        try {

            findFirstTwoFramesHeader(dataIn);

        } finally {
            //zamknac chocby nie wiem co!
            dataIn.close();
        }

        //teraz przeszukanie 'dpbrych' framów
        in = new FileInputStream(file);
        dataIn = new DataInputStream(in);
        long licznik = 0;
        try {

            int tempint = 0;
            MP3FrameHeader fhtemp = fh1; // zaczynamy od 1 framea
            byte[] arrBits;

                // 1. --- 
            fhIlosc = 0; // 1 frame już jest
            fhStegIlosc = 0;
            licznik += fhtemp.getLocation();
            dataIn.skip(licznik);


            while (true) {

                // 1. pobierz nagłówek
                tempint = dataIn.readInt();

                // 2. --- czy to fh?
                if(fhtemp.init(tempint)) {
                    fhIlosc++;

                    // 3. --- wczytaj całość
                    arrBits = new byte[fhtemp.length()-4];
                    dataIn.readFully(arrBits);

                    // 4. --- czy jest steganowalny ?
                    if(MP3FrameHeader.isStegowalny( arrBits ) ){
                        fhStegIlosc++;

                    }

                    // 6. --- czy EOT ?
                    licznik += fhtemp.length();
                        // -4 bo przed ?
                    if(licznik>=id3TAGloc-4) break;

                }
                else
                    throw new StegException(new Exception("steg: mp3 frame header nie jest fh int="+tempint+" w pliku pozycja:"+licznik), StegException.BLAD_WERSJA_NIEOBSLUGIWANA, "Ramka MP3 nie została znaleziona,\n upewnij się iż plik jest typu MPEG I LayerIII" );

            }

        }catch(EOFException eof){ throw new StegException(new Exception("steg: mp3 frame header EOF licznik="+licznik), StegException.BLAD_WERSJA_NIEOBSLUGIWANA, "Plik MP3 jest niepoprawny,\n upewnij się iż plik jest typu MPEG I LayerIII" );
        }finally {
            //zamknac chocby nie wiem co!
            dataIn.close();
        }



    }

    /**
     * Funkcja steg z możliwością monitorowania procesu.
     * @return
     */
    @Override
    public Void doInBackground() {
        try {
            stegProcess();

        // -------------------------- INFO -----------------------------
        //wychodzi na to że wg Google Exceptiony wyżucae przez tą funckje doInB..
        // nidzie nie są łapane, zatem trzeba je przechować i o nich pamiętać, MASAKRA1!!
        //Exception przerywa oczywwiście działanie funkcji, a więc stąd idzie do done()
        } catch (StegException ex) {

            stegEx = ex;

        } catch (Exception ex) {

            stegEx = new StegException(ex, StegException.BLAD_UNSUPORTED, "");
        //this.firePropertyChange("state", StateValue.STARTED, StateValue.DONE);
        }
        return null;

    }

    @Override
    public void done() {

        /*W sumie to nic.*/
    }

    public void unsteg(UnSekret unpaczka) throws StegException, IOException {


            //2. otworz odczyt/zapis, bo jak belad to po co wpisywac
            OutputStream out = new FileOutputStream( unpaczka.getFile() ); //wczytujemy tam gdzie wskazuje unpaczka
            DataOutputStream undataOut = new DataOutputStream(out);

            InputStream in = new FileInputStream( this.getFile() ); //z tegoż pliku
            DataInputStream undataIn = new DataInputStream(in);

        try {

            //3 przeskocz nagowek
            undataIn.skipBytes( (int) fh1.getLocation());

            //4 wczytaj dane
            byte bSteg,bInfo=0;
            int bns = unpaczka.getSkBitowNaSample();

            // stworz maske
            byte maska = (byte) 0x7F;
            if(bns!=8)
                maska >>= 7-bns;
            else
                maska = (byte) 0xFF;

            int licznikBitow = 8-bns;

            int licznikTaga = 0;
            boolean fHaslo = true;
            int ileSczytano = 0;
            byte[] tagBits = new byte[13];
            SekretInfo sinfo = new SekretInfo();

            while( true ) {

                // reset przesumiecia
                if(licznikBitow<0) {

    /***************************************************************************
     * TO DO:
     * Ehh this is bad okey, this is really bad ...
     *
     * rozwiazanie na tenczas na szybko bo mnie sie nie chce
     */
//byc moze tag
if(licznikTaga<13) {

    //zapis bo moze to tag
    tagBits[licznikTaga] = bInfo;



    if(++licznikTaga==13) {
        //wczytano cały tag, ale czy napewno ?
        UnSekret.parseSekretTag(sinfo, tagBits);

// blad, oczekiwano znacznika a nie ma lub znacznik jest wiec spadowa, bo moze byc hasko
        if(unpaczka.isZnacznik()!= sinfo.isTag() )
            throw new StegException(new Exception("steg rozny tag() set"), StegException.BLAD_ZLY_FORMAT_PLIKU,"W pliku przeszukiwanym wartość znacznik jest równa = " + sinfo.isTag() );

        if(unpaczka.isCzyHaslo()!= sinfo.isHaslo() )
            throw new StegException(new Exception("steg: haslo wymagane"), StegException.BLAD_ZLY_FORMAT_PLIKU,"Brak dostępu!" );

        //zapiszmy teraz to, badz nie
        if(!sinfo.isTag() ) undataOut.write( tagBits );

    }

}else{

    // byc moze haslo
    if( fHaslo && licznikTaga>=13 && licznikTaga<53 && sinfo.isHaslo() ) { // 45 to dlugosc haszu

        // ale potem sprawdzimy tu haslo

        System.out.println(licznikTaga +":-<>-:"+(char)bInfo);

        if( bInfo!=unpaczka.getSkHaslo()[licznikTaga-13] )
            throw new StegException(new Exception("steg: zle haslo:"+(char)tagBits[licznikTaga-13]+"!="+(char)unpaczka.getSkHaslo()[licznikTaga-13]), StegException.BLAD_ZLY_FORMAT_PLIKU,"Złe hasło!" );
        licznikTaga++;
    }else{

        //czy koniec wiadomosci wg taga ?
        if(sinfo.isTag() && ileSczytano>=sinfo.getLength() )
            break;

        // JUZ NE TAG WIEC ZAPIS DO pliku
        undataOut.writeByte( bInfo );
        ileSczytano++;

    }
    fHaslo = !fHaslo;
}
// ============= THE END TO DO  THE END TO DO  THE END TO DO  THE END TO DO ====



                    licznikBitow = 8-bns;
                    bInfo=0;

                }

                //pobranie bitow
                bSteg = getNextInfoBit(undataIn);
                bSteg &= maska;

                //przesuniecie pobranych bitow
                bSteg <<= licznikBitow;

                //uzulelnienie informacji
                bInfo |=bSteg;

                licznikBitow-=bns;


                if(usfEOT) break;



            }


        }catch(EOFException eofe) {
            //jak sie zdarzy
            throw new StegException(eofe, StegException.BLAD_ZLY_FORMAT_PLIKU,"Plik jest krótszy niż by to wynikało z Frame Header.");

        }finally{
            undataOut.close();
            undataIn.close();
        }

        //teraz nalezalo by wiadomosc odzipowac !!!



        // i wyzerować zmienne
        usPozInFrame = 0;
        usByteFrame = new byte[0];
        usfEOT = false;
    }

// =============================== FUNKCJE DODATKOWE ===========================
    @Override
    public long getPojemnosc() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public MojaLinkedHashMap getMetaDane() {

        MojaLinkedHashMap map = new MojaLinkedHashMap();
        String temp;

        map.put("Nazwa", plik.getName());
        map.put("Rozmiar pliku", (plik.length()));
        map.put("Rozmiar pliku", (plik.length()));

        if (id3Is) {
            temp = "jest";
        } else {
            temp = "brak";
        }
        map.put("Tag ID3v2", temp);
        if (id3Is) {
            map.put("ID3v2 ver.", id3ver);
            map.put("Długość ID3v2", id3Len);
            //flagi
            map.put("flagUnsynchronisation", flagUnsynchronisation);
            map.put("flagExtendedHeader", flagExtendedHeader);
            map.put("flagExperimentalIndicator", flagExperimentalIndicator);
            map.put("flagFooterPresent", flagFooterPresent);
        }
        map.put("---", "---"); // przerwa

        if (id3TAGIs) {
            temp = "jest";
        } else {
            temp = "brak";
        }
        map.put("Tag ID3v1", temp);
        if (id3TAGIs) {
            map.put("Pozycja ID3v1", id3TAGloc);
        }
        map.put("--", "---"); // przerwa

        if (fhIs) {temp = "jest";} else {temp = "brak";}
        map.put("Frame", temp);
        if (fhIs)
        {map.put("Pozycja Frame", fh1.getLocation()+" [#"+Long.toHexString(fh1.getLocation())+"]" );
        map.put("Ilość frame", fhIlosc );
        map.put("Ilość stegowalnych frame", fhStegIlosc );}
        map.put("-", "---"); // przerwa

        if (fh2Is) {temp = "jest";} else {temp = "brak";}
        map.put("Frame nr2", temp);
        if (fh2Is)
        {map.put("Pozycja Frame nr2", fh2.getLocation()+" [#"+Long.toHexString(fh2.getLocation())+"]");
        map.put("Mpeg", parseMpegVer(fh1.getMpegVer()));
        map.put("Layer", parseMpegLayer(fh1.getMpegVer()));
        map.put("Rozmiar Frame", fh1.length()+" [#"+Integer.toHexString(fh1.length())+"]" );
        map.put("Protection", parseProtection(fh1.getPrivateBit()));
        map.put("Bitrate", parseBitrate(fh1.getBitrateBit()) );
        map.put("Sampling Rate Frequency", parseSamplingRate(fh1.getSamplingRateBit()) );
        map.put("Padding", fh1.getPadding());
        map.put("Private Bit", fh1.getPrivateBit());
        map.put("Channel Mode", parseChannelMode(fh1.getChannelMode()));
        map.put("Mode Extension", fh1.getModeExtension());
        map.put("Copyright", parseCopyright(fh1.getCopyright()));
        map.put("Original", parseOriginal(fh1.getOriginal()));
        map.put("Emphasis", parseEmphasis(fh1.getEmphasis()));}


        return map;
    }

    public void generateRandom(int width, int height) throws StegException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

// =============================== FUNKCJE dziwne ==============================

// =============================================================================
// =============================== FUNKCJE PRYWATNE ============================
    @Override
    public void stegProcess() throws StegException {


        DataOutputStream dataOut;
        DataInputStream dataIn; // nie wiem czy poprawnie to bez finally ???

        try {
            //1 przygotowanie Sekret

            //plik z sekretem, może ulec zmianie po gzipie,
            //gzip owany plik wzucimy do sekretu, a potem go zastapimy starym
            File tempSekretFile = sekret.getFile();

            initSekret();

            //tearaz 50% powinno być ustawionr
            // progress start
            addProgressInPercent(1);


            // ================== WŁAŚCIWA STEGANOGRAFIA



            //otwarcie pliku wzorca

            processNag = "Krok  4/9: rozpoczęcie steganografi";
            processMsg += " ..rozpoczęcie steganografi\n ..przygotowanie danych\n";
            addProgressInPercent(1);

            InputStream in = new FileInputStream(plik);
            dataIn = new DataInputStream(in);

            // otwarcie pliku wzorca+info
            OutputStream out = new FileOutputStream(fileOut);
            dataOut = new DataOutputStream(out);

            processNag = "Krok  5/9: otwarcie plików";
            processMsg += " ..otwarcie strumienia: " + plik.getAbsolutePath() + "\n";
            processMsg += " ..otwarcie strumienia: " + fileOut.getAbsolutePath() + "\n";
            addProgressInPercent(1);


            //start, init proces
            sekret.start();



            //4 wczytaj dane
            processNag = "Krok  6/9: steganowanie";
            processMsg += " ..steganowanie we framach ["+fhIlosc+"/"+fhStegIlosc+"]bitach \n";
            addProgressInPercent(1);



            double proc = (double)50 / fhIlosc; //50% ma to zajac


                // --- STEGANUJEMY  !!!!!!!!!!! ============================= 
            int tempint = 0;
            MP3FrameHeader fhtemp = fh1; // zaczynamy od 1 framea
            byte[] arrBits;

            int frameStegnietych = 0; // ile uzytych framow
            int frameCheked = 0; // ile czeknietych

            // przepisz do 1 frame
            int licznik = 0;
            while (in.available() != 0 && licznik++ < fh1.getLocation()) {
                dataOut.write(dataIn.read());
            }

            processNag = "Krok  7/9: przepisano tagi";
            processMsg += " ..wczytano dane tagów: bitów [" + licznik + "] \n";
            addProgressInPercent(1);

            while (true) {

                // 1. pobierz nagłówek
                tempint = dataIn.readInt();

                // 2. --- czy to fh?
                if(fhtemp.init(tempint)) {
                    frameCheked++;
                    addProgressInPercent(proc);

                    // 3. --- wczytaj całość
                    arrBits = new byte[fhtemp.length()-4];
                    dataIn.readFully(arrBits);

                    // 4. --- czy jest steganowalny ?
                    if(MP3FrameHeader.isStegowalny( arrBits ) ){
                        frameStegnietych++;

                        // zmien strukture frama
                        stegInONeFrame(arrBits);


                    }

                    //zapisz nowa mp3
                    dataOut.writeInt(tempint);
                    dataOut.write(arrBits);

                    //czy seklret caly ?
                    if(sekret.isEOF())break;


                    // 6. --- czy EOT ?
                    licznik += fhtemp.length();
                        // -4 bo przed ?
                    if(licznik>=id3TAGloc-4)
                        throw new StegException(new Exception("steg: pozycja:"+licznik+"/"+plik.length() ), StegException.BLAD_ZLY_FORMAT_PLIKU, "Wiadomość nie zmieściła się!" );

                }
                else
                    throw new StegException(new Exception("steg: mp3 frame header nie jest fh int="+Integer.toHexString(tempint)+" w pliku pozycja:"+licznik), StegException.BLAD_WERSJA_NIEOBSLUGIWANA, "Ramka MP3 nie została znaleziona,\n upewnij się iż plik jest typu MPEG I LayerIII" );

            }




            processNag = "Krok  8/9: dopełnianie pliku";
            processMsg += " ..steganowano [" + frameStegnietych + "/"+frameCheked+"]bitów \n";
            processMsg += " ..dopełnianie [" + (plik.length()-licznik) + "]bitów \n";
            addProgressInPercent(1);


            //5 wczytaj wszystko pozostałe
            while (in.available() != 0) {
                licznik++;
                dataOut.write(dataIn.read());
                //prog
                if(licznik%fh1.length()==0) // co kazdy frame, blad +1 padding pomijam
                    addProgressInPercent(proc);
            }
            dataOut.close();
            dataIn.close();

            // jesli byl gzip to sekretme byl ten plik przed gzipowaniem, trzeba go przywrocic
            sekret.setFile(tempSekretFile);


            processNag = "Krok  9/9: zakańczanie";
            processMsg += " ..zamykanie strumieni [OK] \n"+licznik+"]["+plik.length();
            processMsg += " ..zczyszczenie bufora [OK] \n";
            processMsg += " ..GIZP cancel [OK] \n";
            addProgressInPercent(1);



        } catch (EOFException eofe) {
            //jak sie zdarzy
            throw new StegException(eofe, StegException.BLAD_ZLY_FORMAT_PLIKU, "Za mało miejsca aby schować wszystkie dane.");

        } catch (FileNotFoundException ex) {
            throw new StegException(ex, StegException.BLAD_MODYFIKACJI_PLIKU, "");
        } catch (IOException ex) {
            throw new StegException(ex, StegException.BLAD_MODYFIKACJI_PLIKU, "");
        }

    }

    int usPozInFrame = 0;
    byte[] usByteFrame = new byte[0];
    int usIleFram = 0;
    private byte getNextInfoBit(DataInputStream undataIn) throws IOException, StegException {

        if(usPozInFrame>=usByteFrame.length) {
            usByteFrame = getNextUnstegFrame(undataIn);
            usPozInFrame = 40;
        }

        return usByteFrame[usPozInFrame++]; // zwroc tegoz bita

    }

    private byte[] getNextUnstegFrame(DataInputStream undataIn) throws IOException, StegException {

        byte[] usArrBits;
        MP3FrameHeader usFhtemp = new MP3FrameHeader();
        int usTempint;

        while(true) {

            //czy juz all
            if(usIleFram>=fhIlosc){usfEOT = true; return new byte[50];}//zwroc 0 i wlacz flage

            // 1. pobierz nagłówek
            usTempint = undataIn.readInt();

            // 2. --- czy to fh?
            if(usFhtemp.init(usTempint)) {
                //addProgressInPercent(proc);

                usIleFram++;

                // 3. --- wczytaj całość
                usArrBits = new byte[usFhtemp.length()-4];
                undataIn.readFully(usArrBits);

                // 4. --- czy jest steganowalny ?
                if(MP3FrameHeader.isUnStegowalny( usArrBits ) ){

                    return usArrBits;
                }


            }
            else
                throw new StegException(new Exception("steg: mp3 frame header nie jest fh int="+Integer.toHexString(usTempint) ), StegException.BLAD_WERSJA_NIEOBSLUGIWANA, "Ramka MP3 nie została znaleziona,\n upewnij się iż plik jest typu MPEG I LayerIII" );

        }

    }

    private void stegInONeFrame(byte[] arr) throws IOException {

        int licz = 39;
        int bMsg,bSteg;
        int bns = sekret.getBitowNaSample();

        //zacznik
        arr[36] = TAG_ZNACZNIK[0];
        arr[37] = TAG_ZNACZNIK[1];
        arr[38] = TAG_ZNACZNIK[2];
        arr[39] = TAG_ZNACZNIK[3];

            while (licz++ < arr.length) {

                bMsg = sekret.getNextMessageBit();

                //czy już
                if (sekret.isEOF()) {
                    break;
                }
                
                // pobierz byte
                bSteg = arr[licz];

                // czyszczenie bitow i dodanie
                bSteg >>= bns;
                bSteg <<= bns;
                bSteg |= bMsg;

                //wpisz biciki
                arr[licz] = (byte) bSteg;


                //


            }
    }

    /**
     * Szuka pierwszego Frame Headera.
     *
     * @param dataIn
     * @throws java.io.IOException
     * @throws steg.pliki.StegException
     */
    private void findFirstTwoFramesHeader(DataInputStream dataIn) throws IOException, StegException {

        try{

        //omijamy ID3 tag jesli jest, szukamy nie dalej niż do 'TAG' id3v1
        long licznik = id3Len;
        dataIn.skip(licznik);

        int pierwszeCztery;
        int dodatkowy = 0;

        pierwszeCztery = dataIn.readInt();

        while (dataIn.available() != 0 && licznik++ < id3TAGloc) {

            // get 4 bity i sprawdz, jesli nie, to wez 3 bity mlodsze i
            // jeden nowy XXXN , potem XXNM itd, ponieważ tag może być
            // wszędzie

            if (fh1.init(pierwszeCztery)) {
                fhIs = true;
                fh1.setLocation(licznik-1);//bo od tego co ostatnio czytane

                // teraz pozycje drugiego, ktory powinien być tam gdzie być powinien on
                //skaczemy do drugiej frame
                dataIn.skip(fh1.length()-4); // bo 1 inta(fh) już pobrano
                licznik+=fh1.length();
                pierwszeCztery = dataIn.readInt();
                if (fh2.init(pierwszeCztery)) {
                    fh2Is = true;
                    fh2.setLocation(licznik - 1);



                    return;

                }else{

                    //****************************///
                    // TO NIE MUSI OZNACZAĆ ŻE MP3 JEST UsZKODZoNE
                    // należy zacząć szukać nowego header
                    //return; // 1 true drugie false bez bleduto znak ze trzeba szukac

                    // ale tymczasowo not suported
                    throw new StegException(new Exception("steg ex, brak framea, EOF"), StegException.BLAD_WERSJA_NIEOBSLUGIWANA, "Frame nie został znaleziony,\n upewnij się iż plik jest typu MPEG I LayerIII" );
                    
                }
            }

            // doklejamy AA AA AA NN
            dodatkowy = dataIn.readUnsignedByte();
            pierwszeCztery = pierwszeCztery << 8;
            pierwszeCztery |= (0xFF & dodatkowy);

        }

    }catch(EOFException oef) {
        // belad jak ponizej
    }

        throw new StegException(new Exception("steg ex, brak framea, EOF"), StegException.BLAD_WERSJA_NIEOBSLUGIWANA, "Frame nie został znaleziony,\n upewnij się iż plik jest typu MPEG I LayerIII" );
    }



    /**
     * Wczytuje dane z ID3 tag.
     * Tylko dla wersji 03.xx oraz 04.xx i tylko dane potrzebne dla steganografi.
     * 
     * @param in
     * @throws java.io.IOException
     */
    private void readID3Tag(DataInputStream in) throws IOException, StegException {

        //sprawdzenie wersji
        int ver = in.readUnsignedByte();
        if (ver < 3 && ver != 4) {
            throw new StegException(new Exception("steg ex"), StegException.BLAD_WERSJA_NIEOBSLUGIWANA, "ID3 tag różny od 03.xx lub 04.xx = " + ver);
        }

        //pobranie wersji
        id3ver = ver + "." + in.readUnsignedByte();

        // czytamy flagi
        int iFlags = in.readUnsignedByte();
        flagUnsynchronisation = ((iFlags & 0x80) != 0);
        flagExtendedHeader = ((iFlags & 0x40) != 0);
        flagExperimentalIndicator = ((iFlags & 0x20) != 0);
        flagFooterPresent = ((iFlags & 0x10) != 0); //w wersji 3 nie ma tej flagi

        if ((iFlags & 0x1f) > 0 && ver == 3) {
            // nieznane flagi maja byc wygaszone,
            throw new StegException(new Exception("steg ex"), StegException.BLAD_WERSJA_NIEOBSLUGIWANA, "ID3 tag: flagi nie są wygaszone, błąd wersji: " + id3ver);
        }
        if ((iFlags & 0x0f) > 0 && ver == 4) {
            // nieznane flagi maja byc wygaszone
            throw new StegException(new Exception("steg ex"), StegException.BLAD_WERSJA_NIEOBSLUGIWANA, "ID3 tag: flagi nie są wygaszone, błąd wersji: " + id3ver);
        }

        // dlugosc tagu, totalnie wkurzajaca rzecz, 4* 0bbbbbbb - gdzie najstarszy
        // bit sie nie liczy i ma byc 0, jesli nie jest to error , gowno!

        byte[] lenBajty = new byte[4];
        in.readFully(lenBajty);

        if (((lenBajty[0] & 0x80) != 0) ||
                ((lenBajty[1] & 0x80) != 0) ||
                ((lenBajty[2] & 0x80) != 0) ||
                ((lenBajty[3] & 0x80) != 0)) {
            // jeslico s z przedu bylo 0 em to error
            throw new StegException(new Exception("długość nie powinna mieć 0 z przodu!"), StegException.BLAD_WERSJA_NIEOBSLUGIWANA, "ID3 tag: długość jest źle zdefiniowana, błąd wersji: " + id3ver);
        }

        // teraz trzeba pousuwac 0 z przodu, masakra ile sie czlowek moze nameczyc :/
        // masakra jak mu bardzo moze pomuc google :D
        id3Len |= ((lenBajty[0] & 0x7f) << (3 * 7));
        id3Len |= ((lenBajty[1] & 0x7f) << (2 * 7));
        id3Len |= ((lenBajty[2] & 0x7f) << (1 * 7));
        id3Len |= ((lenBajty[3] & 0x7f) << (0 * 7));



    }

    // --- parsery z bitów na tekst ładn
    private String parseMpegVer(int fh) {

        /*MPEG Audio version ID
        00 - MPEG Version 2.5 (later extension of MPEG 2)
        01 - reserved
        10 - MPEG Version 2 (ISO/IEC 13818-3)
        11 - MPEG Version 1 (ISO/IEC 11172-3) */

        if (fh == 0) {
            return "MPEG Version 2.5";
        }
        if (fh == 1) {
            return "reserved";
        }
        if (fh == 2) {
            return "MPEG Version 2";
        }
        if (fh == 3) {
            return "MPEG Version 1";
        }

        return "błąd odczytu";

    }
    private String parseMpegLayer(int fh) {
        /* 	Layer description
        00 - reserved
        01 - Layer III
        10 - Layer II
        11 - Layer I */

        if (fh == 0) {
            return "reserved";
        }
        if (fh == 1) {
            return "Layer III";
        }
        if (fh == 2) {
            return "Layer II";
        }
        if (fh == 3) {
            return "Layer I";
        }

        return "błąd odczytu";
    }
    private String parseBitrate(int fh) {
        /*
         * TYLKO 3 KOLUMNA DLA LAyer 1 mpeg 3 !!!
         *
        0000  	free  	free  	free  	free  	free
        0001 	32 	32 	32 	32 	8
        0010 	64 	48 	40 	48 	16
        0011 	96 	56 	48 	56 	24
        0100 	128 64 	56 	64 	32
        0101 	160 80 	64 	80 	40
        0110 	192 96 	80 	96 	48
        0111 	224 112 96 	112 56
        1000 	256 128 112 128 64
        1001 	288 160 128 144 80
        1010 	320 192 160 160 96
        1011 	352 224 192 176 112
        1100 	384 256 224 192 128
        1101 	416 320 256 224 144
        1110 	448 384 320 256 160
        1111 	bad bad bad bad bad */

        if (fh == 0)return "free";
        if (fh == 1)return "32";
        if (fh == 2) return "40";
        if (fh == 3)   return "48";
        if (fh == 4)   return "56";
        if (fh == 5)   return "64";
        if (fh == 6)   return "80";
        if (fh == 7)   return "96";
        if (fh == 8)   return "112";
        if (fh == 9)   return "128";
        if (fh == 10)   return "160";
        if (fh == 11)   return "192";
        if (fh == 12)   return "224";
        if (fh == 13)   return "256";
        if (fh == 14)   return "320";
        if (fh == 15)   return "bad";

        return "błąd odczytu";
    }
    private String parseSamplingRate(int fh) {
        /*
         TYLKO DLA MPEG 1
        bits  	MPEG1  	MPEG2  	MPEG2.5
        00 	44100 Hz 	22050 Hz 	11025 Hz
        01 	48000 Hz 	24000 Hz 	12000 Hz
        10 	32000 Hz 	16000 Hz 	8000 Hz
        11 	reserv. 	reserv. 	reserv. */

        if (fh == 0)return "44100 Hz";
        if (fh == 1)return "48000 Hz";
        if (fh == 2) return "32000 Hz";
        if (fh == 3) return "reserv";

        return "błąd odczytu";
    }
    private String parseChannelMode(int fh) {
        /*
        Channel Mode
        00 - Stereo
        01 - Joint stereo (Stereo)
        10 - Dual channel (2 mono channels)
        11 - Single channel (Mono) */

        if (fh == 0)return "Stereo";
        if (fh == 1)return "Joint stereo (Stereo)";
        if (fh == 2) return "Dual channel (2 mono channels)";
        if (fh == 3) return "Single channel (Mono)";

        return "błąd odczytu";
    }
    private String parseCopyright(int fh) {
        /*
        Copyright
        0 - Audio is not copyrighted
        1 - Audio is copyrighted*/

        if (fh == 0)return "Not Copyrighted";
        if (fh == 1)return "Copyrighted";

        return "błąd odczytu";
    }
    private String parseOriginal(int fh) {
        /*
        Original
        0 - Copy of original media
        1 - Original media */

        if (fh == 0)return "Nie orginalne";
        if (fh == 1)return "Orginalne";

        return "błąd odczytu";
    }
    private String parseEmphasis(int fh) {
        /*
            Emphasis
        00 - none
        01 - 50/15 ms
        10 - reserved
        11 - CCIT J.17*/

        if (fh == 0)return "none";
        if (fh == 1)return "50/15 ms";
        if (fh == 2) return "reserved";
        if (fh == 3) return "CCIT J.17";

        return "błąd odczytu";
    }
    private String parseProtection(int fh) {
        /*
        Protection bit
        0 - Protected by CRC (16bit CRC follows header)
        1 - Not protected*/

        if (fh == 0)return "Protected by CRC";
        if (fh == 1)return "brak";

        return "błąd odczytu";
    }



// ------------------------------------- KLASY ----------------------------------
    //else
    @Deprecated
    public void unsteg(File fileIn, UnSekret paczka) throws StegException, IOException {
        throw new UnsupportedOperationException("Not supported yet.");
    }



}
